package pri.damai.xiaowu.common.log.aspect;

import pri.damai.xiaowu.common.core.constant.HttpHeaderKeyConstant;
import pri.damai.xiaowu.common.core.util.IpUtils;
import pri.damai.xiaowu.common.log.LogAspectProperties;
import pri.damai.xiaowu.common.log.anno.XiaoWuLog;
import pri.damai.xiaowu.common.log.model.AuditLog;
import pri.damai.xiaowu.common.log.service.LogOutPutService;
import pri.damai.xiaowu.common.log.trace.ThreadMdcUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 日志 aop 配置类
 * @Desc
 * @Author DaMai
 * @Date 2021/5/14 11:20
 * 但行好事，莫问前程。
 */
@ConditionalOnProperty(name = "xiaowu.log.enable", havingValue = "true")
@Configuration
@Aspect
@Slf4j
public class XiaoWuLogAspect implements ApplicationContextAware {

    final static String DEFAULT_LOGOUTPUTSERVICE_IMPL_BEAN_NAME = "slf4j";

    LogOutPutService logOutPutService;

    @Bean
    @ConditionalOnMissingBean
    public LogAspectProperties logAspectProperties() {
        return new LogAspectProperties();
    }

    /**
     * 被 {@link XiaoWuLog} 注解的类
     * @apiNote [附加描述]
     * @author DaMai
     * @date 2021/5/14 14:19
     */
    @Pointcut(value = "@within(pri.damai.xiaowu.common.log.anno.XiaoWuLog)")
    private void methodPointcut() {
    }

    /**
     * 被 {@link XiaoWuLog} 注解的方法
     * @apiNote [附加描述]
     * @author DaMai
     * @date 2021/5/14 14:03
     */
    @Pointcut(value = "@annotation(pri.damai.xiaowu.common.log.anno.XiaoWuLog)")
    private void objectPointcut() {
    }

    /**
     * 处理切面日志
     *
     * @param pjp 处理点
     * @return Object
     */
    @Around("methodPointcut() || objectPointcut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        Object result = pjp.proceed();

        // 处理日志信息
        AuditLog auditLog = convertToAuditLog(pjp, request, startTime, result);
        logOutPutService.outPutLog(auditLog);
        return result;
    }

    /**
     * 处理日志信息
     * @apiNote [附加描述]
     * @param pjp 植入点
     * @param request 请求
     * @param startTime 开始时间
     * @param result 响应体业务内容
     * @return com.ruiruan.log.model.AuditLog
     * @author DaMai
     * @date 2021/5/14 14:19
     */
    private AuditLog convertToAuditLog(ProceedingJoinPoint pjp, HttpServletRequest request,
                                       long startTime, Object result) {

        String userId = request.getHeader(HttpHeaderKeyConstant.USER_ID_KEY);
        String traceId = ThreadMdcUtil.getTraceId();
        AuditLog auditLog = new AuditLog();
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        auditLog.setTraceId(traceId);
        auditLog.setUserId(userId);
        auditLog.setIp(IpUtils.getIpAddr(request));
        auditLog.setMethod(request.getMethod());
        auditLog.setParameter(getParameter(method, pjp.getArgs()));
        auditLog.setResult(result);
        auditLog.setSpendTime((int) (endTime - startTime));
        auditLog.setStartTime(startTime);
        auditLog.setUrl(request.getRequestURL().toString());

        return auditLog;
    }

    /**
     * 获取请求参数
     * @apiNote [附加描述]
     * @param method http method
     * @param args 参数
     * @return java.lang.Object
     * @author DaMai
     * @date 2021/5/14 13:54
     */
    private Object getParameter(Method method, Object[] args) {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {

            // 将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }

            // 将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        Map<String, LogOutPutService> beansOfType = applicationContext.getBeansOfType(LogOutPutService.class);
        if (StringUtils.isBlank(logAspectProperties().getOutPutType())) {
            this.logOutPutService = beansOfType.get(DEFAULT_LOGOUTPUTSERVICE_IMPL_BEAN_NAME);
            return;
        }
        this.logOutPutService = beansOfType.get(logAspectProperties().getOutPutType());
        log.debug("【Xiao Wu】审计日志配置成功。日志输出方式：{}。", logAspectProperties().getOutPutType());
    }
}
